home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 4 / Apprentice-Release4.iso / Source Code / Pascal / Snippets / OffscreenToys 1.4 / OffscreenToys.p < prev    next >
Encoding:
Text File  |  1995-12-30  |  24.2 KB  |  756 lines  |  [TEXT/PJMM]

  1. {--------- OFFSCREEN TOYS 1.4 ---------}
  2. {by Ingemar Ragnemalm 1994-1995}
  3.  
  4. {An attempt to make a simple, small, stand-alone, compatible offscreen animation demo.}
  5. {I made this since many people want to learn the internals of animation, which packages}
  6. {like SAT (Sprite Animation Toolkit) and aren't good for, even if I had included source.}
  7. {Source code examples should be small!}
  8.  
  9. {Some good points with Offscreen Toys:}
  10. {- It's free, with full source code.}
  11. {- It's complete; no other libraries needed.}
  12. {- It's small, both the source and the compiled binary.}
  13. {- It is a real Mac application with decent event processing.}
  14. {- It works both with and without Color QuickDraw!}
  15. {- Demonstrates the use of fixed-point numbers for speed and positions.}
  16.  
  17. {Some more questionable points:}
  18. {- It's not designed for re-using for complicated animation projects (which SAT is). The list}
  19. {of sprites is an array with no possibility to handle different kinds of sprites. (That's up to}
  20. {you to add if you want to build upon this.)}
  21. {- It doesn't give the maximum speed possible. You can draw directly to screen (which is}
  22. {a bit awkward to do reasonably safe) or to off-screen (which is much safer).}
  23. {- It is neither MultiFinder-aware nor AppleEvent-aware.}
  24. {- The sprite movement isn't as trivial as it could have been, especially the collision handling}
  25. {but also the fixed-point positions, but hey, I need some fun too! If you want an even smaller}
  26. {demo, check out my demo "MicroAnimation". It is just a page of code!}
  27. {- A few things that should be const'ed are hard-coded, i.e. the size of the icon (32x32) and}
  28. {the number of fixed-point "binary decimals" (4).}
  29.  
  30. {What can you use it for?}
  31. {- Learn the basics of sprite animation.}
  32. {- Learn about GWorld.}
  33. {- Use the glue code for making your program work on non-color Macs. Don't you believe in}
  34. {those old Macs any more? If you make a networked game, you should.}
  35. {- Use as skeleton program.}
  36.  
  37. {Offscreen Toys 1.1 adds better collision handling, making the marbles bounce in a more}
  38. {realistic way.}
  39. {Offscreen Toys 1.2 fixes (?) the bug that sometimes caused a marble to disappear for a}
  40. {few seconds, has better error reporting and works with CodeWarrior. (Original version}
  41. {was Think Pascal only)}
  42. {The "boosted" version uses CopyBits from an offscreen to draw the sprites rather than}
  43. {PlotCIcon. This should speed things up quite a bit.}
  44. {Version 1.3 takes out all reuseable code to a separate file. I hope that will make each part}
  45. {easier to understand and makes the reuseable code easily used from other programs.}
  46. {Version 1.4 joins the standard and boosted versions. The difference was so small anyway.}
  47.  
  48. program OffscreenToys;
  49.     uses
  50. {$IFC UNDEFINED THINK_PASCAL}
  51.         Types, QuickDraw, Events, Windows, Dialogs, Fonts, DiskInit, TextEdit,{}
  52.         Traps, Memory, SegLoad, Scrap, ToolUtils, OSUtils, Menus, Resources,{}
  53.         Packages, Icons, Devices, {}
  54. {$ELSEC}
  55.         InterfacesUI, 
  56. {$ENDC}
  57.         QDOffscreen, Sound, OffscreenToysUtils;
  58.  
  59. { --- PART 1: Variables and constants: -----------------------------------------}
  60.  
  61.     const
  62.         kAppleID = 128;
  63.         kFileID = 129;
  64.         kMBarHeight = 20;        { We assume 20 pixels menu bar for window sizing and dragging.}
  65.  
  66.         kWindID = 128;            { Window resource ID }
  67.         kAboutAlertID = 128;    { Alert resource ID }
  68.  
  69.         kSpriteNumber = 5;        { Number of moving objects }
  70.     var
  71.         gWhoa: Boolean;            { True when we want to quit}
  72.         gCollisionFlag: Boolean;    { Collisions or not?}
  73.         gBoostFlag: Boolean;        { Run boosted?}
  74.  
  75. {Menu handles}
  76.         appleMenu, fileMenu: MenuHandle;
  77.  
  78. {The window we'll be using}
  79.         gWind: WindowPtr;
  80.  
  81. {Our two offscreens:}
  82.         offScreen, backScreen: GrafPtr;
  83.  
  84. {A cicn handle}
  85.         gCicn: CIconHandle;
  86. {A GrafPtr for the "boosted" icon.}
  87.         gBoostCicn: GrafPtr;
  88.  
  89. {Sprite information. In real games, I prefer making a linked list of records, like I do in}
  90. {SAT, and a lot more information for each, but here we want it *simple*.}
  91. {- position: The positions in local coordinates for the window}
  92. {- fixedPos: 16 times position, which gives us fixed-point numbers}
  93. {- speed: Speed vectors that is added to fixedPos for every frame}
  94. {- r: Rectangles used in drawing, for remembering what part of the screen to update}
  95.         position, fixedPos: array[1..kSpriteNumber] of Point;
  96.         speed: array[1..kSpriteNumber] of Point;
  97.         r: array[1..kSpriteNumber] of Rect;
  98.  
  99. {A longint that shows how big the "bowl" is (assumes circular, not oval, bowl!):}
  100.         gBowlSize: Longint;
  101.  
  102. { --- PART 2: Various general, reuseable routines, mostly glue: ---------------------}
  103. {All except Rand moved to OffscreenToysUtils}
  104.  
  105. {Rand: simply make a random number between 0 and range-1.}
  106.  
  107.     function Rand (range: integer): integer;
  108.     begin
  109.         Rand := abs(Integer(Random) mod range);
  110.     end;
  111.  
  112. { --- PART 3: Application specific routines: ---------------------------------}
  113.  
  114. {mouse clicks, keydowns, background tasks and update events: This is where all}
  115. {the action is. :-) I include some empty procedures for you to fill in if you want to}
  116. {use this demo as application shell.}
  117.  
  118. {Mouse click in window content}
  119.  
  120.     procedure DoMouse (where: Point; modifiers: Longint);
  121.     begin
  122.     end;
  123.  
  124. {Keydown.}
  125.  
  126.     procedure DoKey (theKey: Char; modifiers: Longint);
  127.     begin
  128.     end;
  129.  
  130. {DoBackground: repeating tasks - this is called repeatedly, after every event we get.}
  131.  
  132. {Note: If you are making a really Mac-friendly program, this is where you should drive}
  133. {the animation. However, it is hard to get high framerate then, since other programs}
  134. {(the Finder included) will process events which will make it less smooth.}
  135.  
  136.     procedure DoBackground;
  137.         const
  138.             kWallBounce = 7;                            {1/10-ths of speed kept after wallbounce}
  139.             kBallDiameterSquared = 32 * 32;            {Diameter 32, squared}
  140.         var
  141.             tmpRect: Rect;
  142.             i, j: integer;
  143.             vector: Point;
  144.             saveGD: GDHandle;
  145.             savePort: GrafPtr;
  146.             tmpSpeed: Point;
  147.             squaredLength: Longint;
  148.             p1, p2, n1, n2: Point;
  149.  
  150. {Split a vector (v1) into one component parallell to another vector (direction) and one}
  151. {orthogonal to it.}
  152.         procedure SplitVector (v1, direction: Point; var parallell, normal: Point);
  153.             var
  154.                 l2, v1pr: Longint;
  155.         begin
  156. {parallell := direction * (v1 DOT direction) /|direction|**2}
  157. {normal := v1 - parallell}
  158.  
  159.             l2 := direction.h * direction.h + direction.v * direction.v; {Squared length of "direction"}
  160.             v1pr := v1.h * direction.h + v1.v * direction.v; {Scalar product}
  161.  
  162.             parallell.h := direction.h * v1pr div l2;
  163.             parallell.v := direction.v * v1pr div l2;
  164.             normal.h := v1.h - parallell.h;
  165.             normal.v := v1.v - parallell.v;
  166.         end;
  167.  
  168. {A rather boring subroutine that moves the sprites i and j away from each other.}
  169. {I almost didn't want to put this in, making the demo unnecessarily big, but I wanted}
  170. {decent collisions. If anyone can suggest a better (simpler) collision handling for circular}
  171. {objects, I'd be happy to put it in.}
  172. {I use a line drawing algorithm for it. I'm sure there are better ways. This is of questionable}
  173. {value as reuseable code - depends on your application.}
  174.         procedure Separate (i, j: integer);
  175.             var
  176.                 initVector, nowVector: Point;
  177.                 absH, absV: integer;
  178.                 moveH, moveV: integer;
  179.                 frac: integer;
  180. {Normal signum function (which I don't think is in the libs)}
  181.             function Sgn (x: integer): integer;
  182.             begin
  183.                 if x > 0 then
  184.                     Sgn := 1
  185.                 else if x < 0 then
  186.                     Sgn := -1
  187.                 else
  188.                     Sgn := 0;
  189.             end;
  190.  
  191.         begin {Separate}
  192.             frac := 0;
  193.             initVector.h := position[i].h - position[j].h;
  194.             initVector.v := position[i].v - position[j].v;
  195.             absH := abs(initVector.h);
  196.             absV := abs(initVector.v);
  197.             moveH := Sgn(initVector.h);
  198.             moveV := Sgn(initVector.v);
  199.             if moveH = 0 then
  200.                 if moveV = 0 then
  201.                     moveV := 1;
  202.             repeat
  203.                 if absH > absV then
  204.                     begin
  205.                         position[i].h := position[i].h + moveH;
  206.                         position[j].h := position[j].h - moveH;
  207.                         frac := frac + absV;
  208.                         if frac > absH then
  209.                             begin
  210.                                 position[i].v := position[i].v + moveV;
  211.                                 position[j].v := position[j].v - moveV;
  212.                                 frac := frac - absH;
  213.                             end
  214.                     end
  215.                 else
  216.                     begin
  217.                         position[i].v := position[i].v + moveV;
  218.                         position[j].v := position[j].v - moveV;
  219.                         frac := frac + absH;
  220.                         if frac > absV then
  221.                             begin
  222.                                 position[i].h := position[i].h + moveH;
  223.                                 position[j].h := position[j].h - moveH;
  224.                                 frac := frac - absV;
  225.                             end
  226.                     end;
  227.                 nowVector.h := position[i].h - position[j].h;
  228.                 nowVector.v := position[i].v - position[j].v;
  229.             until longint(nowVector.h) * nowVector.h + longint(nowVector.v) * nowVector.v > kBallDiameterSquared;
  230.             fixedPos[i].h := BSL(position[i].h, 4); {Make fixedPos by shifting in the 4 binary "decimals"}
  231.             fixedPos[i].v := BSL(position[i].v, 4);
  232.             fixedPos[j].h := BSL(position[j].h, 4); {Make fixedPos by shifting in the 4 binary "decimals"}
  233.             fixedPos[j].v := BSL(position[j].v, 4);
  234.         end; {Separate}
  235.  
  236.     begin {DoBackground}
  237.         OTGetGWorld(savePort, saveGD);
  238. {1: Erase all sprites from offScreen}
  239. {Note: We keep the rectangles r[i] for erasing on the screen, later.}
  240.         OTSetGWorld(offScreen, nil);
  241.         for i := 1 to kSpriteNumber do
  242.             begin
  243.                 r[i] := gCicn^^.iconBMap.bounds;
  244.                 OffsetRect(r[i], position[i].h, position[i].v);
  245.                 CopyBits(backScreen^.portBits, offScreen^.portBits, r[i], r[i], srcCopy, nil);
  246.             end;
  247. {2: Change the position and speed}
  248.         for i := 1 to kSpriteNumber do
  249.             begin
  250. {Modify fixed-point position by speed}
  251.                 fixedPos[i].h := fixedPos[i].h + speed[i].h;
  252.                 fixedPos[i].v := fixedPos[i].v + speed[i].v;
  253.  
  254. {Make position by shifting away the 4 binary "decimals"}
  255.                 if fixedPos[i].h >= 0 then
  256.                     position[i].h := BSR(fixedPos[i].h, 4)
  257.                 else
  258.                     position[i].h := BitOr(BSR(fixedPos[i].h, 4), $f000);
  259.                 if fixedPos[i].v >= 0 then
  260.                     position[i].v := BSR(fixedPos[i].v, 4)
  261.                 else
  262.                     position[i].v := BitOr(BSR(fixedPos[i].v, 4), $f000);
  263.  
  264. {Keep inside the window}
  265.                 if fixedPos[i].h + speed[i].h < 0 then
  266.                     speed[i].h := abs(speed[i].h) * kWallBounce div 10 + 1;
  267.                 if fixedPos[i].v + speed[i].v < 0 then
  268.                     speed[i].v := abs(speed[i].v) * kWallBounce div 10 + 1;
  269.                 if position[i].h + gCicn^^.iconBMap.bounds.right > offScreen^.portRect.right then
  270.                     speed[i].h := -abs(speed[i].h) * kWallBounce div 10 - 1;
  271.                 if position[i].v + gCicn^^.iconBMap.bounds.bottom > offScreen^.portRect.bottom then
  272.                     speed[i].v := -abs(speed[i].v) * kWallBounce div 10 - 1;
  273.  
  274. {Are we in the bowl? If we are, accelerate towards the center.}
  275.                 vector.h := position[i].h + 16 - BSR(offScreen^.portRect.right, 1);
  276.                 vector.v := position[i].v + 16 - BSR(offScreen^.portRect.bottom, 1);
  277.                 if (vector.h * vector.h + vector.v * vector.v) < gBowlSize then
  278.                     begin
  279.                         speed[i].h := speed[i].h - vector.h div 2;
  280.                         speed[i].v := speed[i].v - vector.v div 2;
  281.                     end;
  282.             end; {position/speed loop}
  283.  
  284. {Check for collisions}
  285.         if gCollisionFlag then
  286.             for i := 1 to kSpriteNumber - 1 do {For all objects except the last}
  287.                 for j := i + 1 to kSpriteNumber do {compare its position to all following objects}
  288.                     begin
  289. {Find the vector between them}
  290.                         vector.h := position[i].h - position[j].h;
  291.                         vector.v := position[i].v - position[j].v;
  292.                         squaredLength := longint(vector.h) * vector.h + longint(vector.v) * vector.v;
  293. {If it is shorter than the diameter of a ball…}
  294.                         if squaredLength >= 0 then {WHY do I get negative values here? Or is that a CW bug that has been fixed?}
  295.                             if squaredLength < kBallDiameterSquared then
  296.                                 begin
  297. {Move them away from each other}
  298.                                     Separate(i, j);
  299. {Swap the speed components that are parallell to "vector" (this allows for "touches", very}
  300. {nice and realistic bounces)}
  301.                                     SplitVector(speed[i], vector, p1, n1);
  302.                                     SplitVector(speed[j], vector, p2, n2);
  303.  
  304.                                     speed[j].h := p1.h + n2.h;
  305.                                     speed[j].v := p1.v + n2.v;
  306.  
  307.                                     speed[i].h := p2.h + n1.h;
  308.                                     speed[i].v := p2.v + n1.v;
  309.  
  310. {Old Offscren Toys just swapped the speed, as commented out below. This is not as realistic.}
  311. {tmpSpeed := speed[i];}
  312. {speed[i] := speed[j];}
  313. {speed[j] := tmpSpeed;}
  314.  
  315. {Play a sound. REMOVED since this isn't a sound demo.}
  316. {if gSoundFlag then}
  317. {if SndPlay(nil, GetNamedResource('snd ', 'Kgck'), false) <> noErr then [Ignore error}
  318.  
  319.                                 end;
  320.                     end; {collision loop}
  321.  
  322. {3: Draw sprites in offScreen}
  323. {Note: PlotCIcon (OTPlotCicn) is not very fast! We can speed it up my pre-drawing them in some}
  324. {offscreen, and CopyBits them from there.}
  325.         for i := 1 to kSpriteNumber do
  326.             begin
  327.                 tmpRect := gCicn^^.iconBMap.bounds;
  328.                 OffsetRect(tmpRect, position[i].h, position[i].v);
  329.                 if gBoostFlag then
  330.                     OTPlotBoostCicn(gBoostCicn, offScreen, tmpRect.topLeft)
  331.                 else
  332.                     OTPlotCicn(gCicn, offScreen, tmpRect);
  333.             end;
  334.  
  335. {4: Copy sprites to the screen (gWind) - both old and new position!}
  336. {Note: Depending on what limitations we have on movement, we may be able to avoid the multiple}
  337. {CopyBitsing here. E.g. if sprites always move a maximum of 2 pixels, we can copy a 2 pixels}
  338. {larger area, etc.}
  339.         OTSetGWorld(gWind, saveGD); {What GD is most appropriate here?}
  340.         for i := 1 to kSpriteNumber do
  341.             begin
  342.                 CopyBits(offScreen^.portBits, gWind^.portBits, r[i], r[i], srcCopy, nil);
  343.                 r[i] := gCicn^^.iconBMap.bounds;
  344.                 OffsetRect(r[i], position[i].h, position[i].v);
  345.                 CopyBits(offScreen^.portBits, gWind^.portBits, r[i], r[i], srcCopy, nil);
  346.             end;
  347.         OTSetGWorld(savePort, saveGD);
  348.     end; {DoBackground}
  349.  
  350. {DoUpdate: handle update events, in this case by copying offScreen to the screen (gWind).}
  351.  
  352. {Note to beginners: A program without update events processing is not a real Mac program!}
  353. {All drawing you do must reach the update event handler in some way, or it might be lost,}
  354. {or worse, partially erased, which is really ugly.}
  355.  
  356.     procedure DoUpdate;
  357.         var
  358.             saveGD: GDHandle;
  359.             savePort: GrafPtr;
  360.     begin
  361.         OTGetGWorld(savePort, saveGD);
  362.         SetPort(gWind); {or OTSetGWorld(gWind, GetMainDevice), in case I forget to et back the device?}
  363.         BeginUpdate(gWind);
  364. {Do drawing here - in this case a CopyBits}
  365.         CopyBits(offScreen^.portBits, gwind^.portBits, gwind^.portRect, gwind^.portRect, srcCopy, nil);
  366.         EndUpdate(gWind);
  367.         OTSetGWorld(savePort, saveGD);
  368.     end; {DoUpdate}
  369.  
  370. {DoAppleMenu and DoFileMenu: handle menu selections}
  371.  
  372.     procedure DoAppleMenu (item: integer);
  373.         var
  374.             str: Str255;
  375.             h: Handle;
  376.             saveGD: GDHandle;
  377.             savePort: GrafPtr;
  378.             ignore: integer;
  379.     begin
  380.         if item = 1 then
  381.             begin
  382.                 if Alert(kAboutAlertID, nil) = 1 then
  383.                     ; {Ignore result}
  384.             end
  385.         else
  386. {Apple menu other than "About": Code from TransSkel}
  387.             begin
  388.                 OTGetGWorld(savePort, saveGD); {I guess GetPort would be ok}
  389.                 GetMenuItemText(appleMenu, item, str);
  390.                 SetResLoad(false);
  391.                 h := GetNamedResource('DRVR', str);
  392.                 SetResLoad(true);
  393.                 if h <> nil then
  394.                     begin
  395.                         ReserveMem(GetResourceSizeOnDisk(h) + $1000);
  396.                         ignore := OpenDeskAcc(str);
  397.                     end;
  398.                 OTSetGWorld(savePort, saveGD);
  399.             end;
  400.     end; {DoAppleMenu}
  401.  
  402.     procedure SynchMenus;
  403.     begin
  404.         CheckItem(fileMenu, 2, gCollisionFlag);
  405.         CheckItem(fileMenu, 3, gBoostFlag);
  406.     end; {SynchMenus}
  407.  
  408.     procedure DoFileMenu (item: integer);
  409.         var
  410.             start, finish, frames: Longint;
  411.             fpsStr: Str255;
  412.     begin
  413.         case item of
  414.             1:
  415. {Run animation without event processing until the user clicks the mouse}
  416. {Note: This runs the animation at maximum speed. In real programs, we}
  417. {must limit the speed with the system clock, e.g. inspect TickCount.}
  418.                 begin
  419.                     start := TickCount;
  420.                     frames := 0;
  421.                     while not Button do
  422.                         begin
  423.                             DoBackground;
  424.                             frames := frames + 1;
  425.                         end;
  426.                     finish := TickCount;
  427.                     NumToString(frames * 60 div (finish - start), fpsStr);
  428.                     ParamText(fpsStr, ' frames/second', '', '');
  429.                     if Alert(129, nil) = 1 then
  430.                         ;
  431.                 end;
  432.             2: 
  433.                 begin
  434.                     gCollisionFlag := not gCollisionFlag;
  435.                     SynchMenus;
  436.                 end;
  437.             3: 
  438.                 begin
  439.                     gBoostFlag := not gBoostFlag;
  440.                     SynchMenus;
  441.                 end;
  442. {Set the flag that tells the program to quit.}
  443.             5: 
  444.                 gWhoa := true;
  445.         end; {case}
  446.     end; {DoFileMenu}
  447.  
  448. { --- PART 4: Event processing: -----------------------------------------}
  449.  
  450. {MenuSelection: Menu selection by mouse or command-key:}
  451.  
  452.     procedure MenuSelection (whatSelection: longInt);
  453.     begin
  454.         case HiWord(whatSelection) of
  455.             kAppleID: 
  456.                 DoAppleMenu(LoWord(whatSelection));
  457.             kFileID: 
  458.                 DoFileMenu(LoWord(whatSelection));
  459.         end; {case}
  460.         HiLiteMenu(0);
  461.     end; {MenuSelection}
  462.  
  463. {MainLoop: get and process events. This is the boring standard part of all programs. I prefer}
  464. {using TransSkel to get rid of it. I don't here since I want this code to be stand-alone.}
  465.  
  466.     procedure MainLoop;
  467.         const
  468.             kSleep = 0; {Real programs may modify the sleep time depending on whether or not they are in the front}
  469.         var
  470.             hasEvent: Boolean;
  471.             theEvent: EventRecord;
  472.             theKey: Char;
  473.             whatSelection: Longint;
  474.             whichPart: integer;
  475.             whichWindow: WindowPtr;
  476.             r: rect;
  477.     begin
  478. {Get the next event. Use WaitNextEvent if possible.}
  479.         if gHasWNE then
  480.             hasEvent := WaitNextEvent(everyEvent, theEvent, kSleep, nil)
  481.         else
  482.             begin
  483.                 SystemTask;
  484.                 hasEvent := GetNextEvent(everyEvent, theEvent);
  485.             end;
  486.  
  487. {OK, so what happened then?}
  488.         if hasEvent then
  489.             case theEvent.what of
  490.                 mouseDown: 
  491.                     begin
  492.                         whichPart := FindWindow(theEvent.where, whichWindow);
  493.                         case whichPart of
  494.                             inMenuBar: 
  495.                                 begin
  496.                                     whatSelection := MenuSelect(theEvent.where);
  497.                                     MenuSelection(whatSelection);
  498.                                 end;
  499.                             inSysWindow: 
  500.                                 SystemClick(theEvent, whichWindow);
  501.                             inGoAway: 
  502.                                 if (TrackGoAway(whichWindow, theEvent.where)) then
  503.                                     gWhoa := true;
  504.                             inDrag: 
  505.                                 begin
  506.                                     if (whichWindow <> FrontWindow) and (BitAnd(theEvent.modifiers, cmdKey) = 0) then
  507.                                         SelectWindow(whichWindow);
  508. {$IFC UNDEFINED THINK_PASCAL}
  509.                                     r := qd.screenBits.bounds;            {How big is the screen? (Note: Don't use screenBits for other things: it isn't a valid BitMap any more!)}
  510. {$ELSEC}
  511.                                     r := screenBits.bounds;            {How big is the screen? (Note: Don't use screenBits for other things: it isn't a valid BitMap any more!)}
  512. {$ENDC}
  513.                                     r.top := r.top + kMBarHeight;        { Skip down past menu bar    }
  514.                                     InsetRect(r, 4, 4);
  515.                                     DragWindow(whichWindow, theEvent.where, r);
  516.                                 end;
  517.                             inGrow: 
  518.                                 ;  {Ignored - we don't resize}
  519.                             inContent: 
  520.                                 if (whichWindow <> FrontWindow) then
  521.                                     SelectWindow(whichWindow)
  522.                                 else
  523.                                     DoMouse(theEvent.where, theEvent.modifiers); {Go to application-specific mouse down handling}
  524.                         end; {case whichPart}
  525.                     end; {mouseDown}
  526.                 keyDown, autoKey: 
  527.                     begin
  528.                         theKey := char(BitAnd(theEvent.message, charCodeMask));
  529.                         if (BitAnd(theEvent.modifiers, cmdKey) <> 0) then
  530.                             MenuSelection(MenuKey(theKey))
  531.                         else
  532.                             DoKey(theKey, theEvent.modifiers);
  533.                     end;
  534.                 updateEvt:
  535. {There's only one window to bother with here, but let's make sure that's the one the Mac wants to update.}
  536.                     if WindowPtr(theEvent.message) = gWind then
  537.                         DoUpdate;
  538. {Handle disk inserts like TransSkel.}
  539.                 diskEvt: 
  540.                     if (HiWord(theEvent.message) <> noErr) then
  541.                         begin
  542.                             DILoad;
  543.                             if DIBadMount(Point($00400040), theEvent.message) = 0 then
  544.                                 ;
  545.                             DIUnload;
  546.                         end; {diskEvt}
  547.                 otherwise {Other events are ignored}
  548.             end; {case}
  549.  
  550.         DoBackground;
  551.     end; {MainLoop}
  552.  
  553. { --- PART 5: Initializations: -----------------------------------------}
  554.  
  555. {OTInit: Initialize global flags, menus and window}
  556.  
  557.     procedure OTInit;
  558.         const
  559. {Trap numbers}
  560.             _WaitNextEvent = $A860;
  561.             _GetCIcon = $AA1E; {E.g. any Color QuickDraw routine}
  562.             k32bQD = $AB1D;
  563.             _SndPlay = $A805;
  564.         var
  565.             appleStr: Str255;
  566.     begin
  567.  
  568. {In case this isn't Think Pascal we have to make the standard inits ourselves.}
  569. {$IFC UNDEFINED THINK_PASCAL}
  570.         InitGraf(@qd.thePort);
  571.         InitFonts;
  572.         InitWindows;
  573.         InitMenus;
  574.         TEInit;
  575.         InitDialogs(nil);
  576. {InitCursor;}
  577.         MaxApplZone;
  578. {$ENDC}
  579.  
  580. {Over to more interesting stuff}
  581.  
  582.         OTInitGlobals;            {Init OffscreenToysUtils}
  583.  
  584.         gWhoa := false;
  585.         gCollisionFlag := false;
  586.  
  587. {We could check with Gestalt instead, but that isn't necessary here since we aren't using any}
  588. {optional services (like QuickTime).}
  589.  
  590. {$IFC UNDEFINED THINK_PASCAL}
  591.         qd.randSeed := TickCount;            {Seed the random number generator - TickCount is good enough.}
  592. {$ELSEC}
  593.         randSeed := TickCount;                {Seed the random number generator - TickCount is good enough.}
  594. {$ENDC}
  595.  
  596. {Get the window, a color window if we are going to use color.}
  597.         if gColorQDFlag then
  598.             gWind := GetNewCWindow(kWindId, nil, WindowPtr(-1))
  599.         else
  600.             gWind := GetNewWindow(kWindId, nil, WindowPtr(-1));
  601.  
  602. {Some menus. We could read these from resources.}
  603.         appleMenu := NewMenu(kAppleID, stringof(char($14)));
  604.         AppendMenu(appleMenu, 'About OffscreenToys…;(-');
  605.         AppendResMenu(appleMenu, 'DRVR');
  606.         InsertMenu(appleMenu, 0);            { put apple menu at end of menu bar }
  607.         fileMenu := NewMenu(kFileID, 'File');
  608.         AppendMenu(fileMenu, 'Try max speed;Collisions;Boost;(-;Quit/Q');
  609.         InsertMenu(fileMenu, 0);            { put file menu at end of menu bar }
  610.         DrawMenuBar;
  611.     end; {OTInit}
  612.  
  613. {OTOffscreensInit: Initialize offscreen grafports (worlds) and draw in them.}
  614.  
  615.     procedure OTOffscreensInit;
  616.         var
  617.             saveGD: GDHandle;
  618.             savePort: GrafPtr;
  619.             thePat: PixPatHandle;
  620.             r: Rect;
  621.             i: integer;
  622.             colorFlag: Boolean;
  623.         const
  624.             patID = 128;
  625.  
  626. {A little routine for setting the forecolor with a single line.}
  627.         procedure OTForeColor (red, green, blue: integer);
  628.             var
  629.                 theColor: RGBColor;
  630.         begin
  631.             theColor.red := red;
  632.             theColor.green := green;
  633.             theColor.blue := blue;
  634.             RGBForeColor(theColor);
  635.         end;
  636.  
  637.     begin {OTOffscreensInit}
  638.         OTGetGWorld(savePort, saveGD);
  639.  
  640.         OTNewGWorld(offScreen, gWind^.portRect);
  641.         OTNewGWorld(backScreen, gWind^.portRect);
  642.  
  643.         OTSetGWorld(backScreen, nil);
  644.  
  645. {Do some drawing in backScreen. First, we paint a pattern (using a 'ppat' resource):}
  646.  
  647. {For drawing the background, let's make a local flag that tells us if we shold draw}
  648. {b/w patterns or color ones.}
  649.         if gColorQDFlag then
  650.             colorFlag := (CGrafPtr(backScreen)^.portPixMap^^.pixelSize > 1)
  651.         else
  652.             colorFlag := false;
  653.  
  654.         if colorFlag then
  655.             begin
  656.                 thePat := GetPixPat(patID);
  657.                 PenPixPat(thePat)
  658.             end
  659.         else
  660.             begin
  661.                 thePat := PixPatHandle(GetResource('ppat', patID));
  662.                 PenPat(thePat^^.pat1Data);
  663.             end;
  664.         PaintRect(backScreen^.portRect);
  665.         PenNormal;
  666.  
  667. {Then we draw some circles.}
  668.  
  669.         r := backScreen^.portRect;
  670.         InsetRect(r, (r.right - r.left) div 8, (r.bottom - r.top) div 8);
  671.         gBowlSize := longint(r.right - r.left) * (r.right - r.left) div 4;        {Tells how big the "bowl" is!}
  672.         if colorFlag then
  673.             begin
  674.                 OTForeColor(-10000, -10000, -10000);
  675.                 PaintOval(r);
  676.             end
  677.         else
  678. {$IFC UNDEFINED THINK_PASCAL}
  679.             FillOval(r, qd.ltGray);
  680. {$ELSEC}
  681.         FillOval(r, ltGray);
  682. {$ENDC}
  683.  
  684.         InsetRect(r, (r.right - r.left) div 8, (r.bottom - r.top) div 8);
  685.         if colorFlag then
  686.             begin
  687.                 OTForeColor(-25000, -25000, -25000);
  688.                 PaintOval(r);
  689.             end
  690.         else
  691. {$IFC UNDEFINED THINK_PASCAL}
  692.             FillOval(r, qd.gray);
  693. {$ELSEC}
  694.         FillOval(r, gray);
  695. {$ENDC}
  696.  
  697.         InsetRect(r, (r.right - r.left) div 6, (r.bottom - r.top) div 6);
  698.         if colorFlag then
  699.             begin
  700.                 OTForeColor(20000, 20000, 20000);
  701.                 PaintOval(r);
  702.             end
  703.         else
  704. {$IFC UNDEFINED THINK_PASCAL}
  705.             FillOval(r, qd.dkGray);
  706. {$ELSEC}
  707.         FillOval(r, dkGray);
  708. {$ENDC}
  709.  
  710.         InsetRect(r, (r.right - r.left) div 5, (r.bottom - r.top) div 5);
  711.         if colorFlag then
  712.             OTForeColor(0, 0, 0);
  713.         PaintOval(r);
  714.  
  715. {Done drawing!}
  716. {For your own hacks, consider using a PICT resource and use GetPicture and DrawPicture to draw the}
  717. {background. Note that you'll need both a color and a b/w picture if you want it to look good in b/w.}
  718.  
  719.         OTSetGWorld(offScreen, nil);
  720.         CopyBits(backScreen^.portBits, offScreen^.portBits, backScreen^.portRect, backScreen^.portRect, srcCopy, nil);
  721.  
  722.         OTSetGWorld(savePort, saveGD);
  723.  
  724. {Get the cicn resource}
  725. {Note: You can, of course, use several cicns and switch between.}
  726.         gCicn := OTGetCicn(128);
  727.         gBoostCicn := OTGetBoostCicn(128);
  728.  
  729. {Initialize the sprite information arrays:}
  730.  
  731.         for i := 1 to kSpriteNumber do
  732.             begin
  733.                 position[i].h := Rand(offScreen^.portRect.right - 32);
  734.                 position[i].v := (i - 1) * (offScreen^.portRect.bottom - 32) div 5 + Rand((offScreen^.portRect.bottom - 32) div 5);
  735.                 fixedPos[i].h := BSL(position[i].h, 4);
  736.                 fixedPos[i].v := BSL(position[i].v, 4);
  737.                 speed[i].h := Random mod 32;
  738.                 speed[i].v := Random mod 32;
  739.             end;
  740.     end; {OTOffscreensInit}
  741.  
  742. { --- MAIN PROGRAM BODY: -----------------------------------------}
  743.  
  744. begin
  745.     OTInit;                    {General initializations}
  746.     OTOffscreensInit;        {Set up the offscreen grafports}
  747.     InitCursor;                {Set the cursor to arrow in case it isn't.}
  748.  
  749. {Run until quit or click in the close box.}
  750.     repeat
  751.         MainLoop;
  752.     until gWhoa;
  753.  
  754. {No cleanup is necessary here.}
  755. {We could DisposeGWorld, but that isn't necessary when we are quitting.}
  756. end.